// ASM: a very small and fast Java bytecode manipulation framework
// Copyright (c) 2000-2011 INRIA, France Telecom
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 1. Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.
// 3. Neither the name of the copyright holders nor the names of its
//    contributors may be used to endorse or promote products derived from
//    this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
// THE POSSIBILITY OF SUCH DAMAGE.
package cn.taketoday.context.asm;

/**
 * A {@link ClassVisitor} that generates a corresponding ClassFile structure, as
 * defined in the Java Virtual Machine Specification (JVMS). It can be used
 * alone, to generate a Java class "from scratch", or with one or more
 * {@link ClassReader} and adapter {@link ClassVisitor} to generate a modified
 * class from one or more existing Java classes.
 *
 * @author Eric Bruneton
 * @see <a href=
 * "https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html">JVMS
 * 4</a>
 */
public class ClassWriter extends ClassVisitor {

  /**
   * A flag to automatically compute the maximum stack size and the maximum number
   * of local variables of methods. If this flag is set, then the arguments of the
   * {@link MethodVisitor#visitMaxs} method of the {@link MethodVisitor} returned
   * by the {@link #visitMethod} method will be ignored, and computed
   * automatically from the signature and the bytecode of each method.
   *
   * <p>
   * <b>Note:</b> for classes whose version is {@link Opcodes#V1_7} of more, this
   * option requires valid stack map frames. The maximum stack size is then
   * computed from these frames, and from the bytecode instructions in between. If
   * stack map frames are not present or must be recomputed, used
   * {@link #COMPUTE_FRAMES} instead.
   *
   * @see #ClassWriter(int)
   */
  public static final int COMPUTE_MAXS = 1;

  /**
   * A flag to automatically compute the stack map frames of methods from scratch.
   * If this flag is set, then the calls to the {@link MethodVisitor#visitFrame}
   * method are ignored, and the stack map frames are recomputed from the methods
   * bytecode. The arguments of the {@link MethodVisitor#visitMaxs} method are
   * also ignored and recomputed from the bytecode. In other words,
   * {@link #COMPUTE_FRAMES} implies {@link #COMPUTE_MAXS}.
   *
   * @see #ClassWriter(int)
   */
  public static final int COMPUTE_FRAMES = 2;

  // Note: fields are ordered as in the ClassFile structure, and those related to
  // attributes are
  // ordered as in Section 4.7 of the JVMS.

  /**
   * The minor_version and major_version fields of the JVMS ClassFile structure.
   * minor_version is stored in the 16 most significant bits, and major_version in
   * the 16 least significant bits.
   */
  private int version;

  /**
   * The symbol table for this class (contains the constant_pool and the
   * BootstrapMethods).
   */
  private final SymbolTable symbolTable;

  /**
   * The access_flags field of the JVMS ClassFile structure. This field can
   * contain ASM specific access flags, such as {@link Opcodes#ACC_DEPRECATED},
   * which are removed when generating the ClassFile structure.
   */
  private int accessFlags;

  /** The this_class field of the JVMS ClassFile structure. */
  private int thisClass;

  /** The super_class field of the JVMS ClassFile structure. */
  private int superClass;

  /** The interface_count field of the JVMS ClassFile structure. */
  private int interfaceCount;

  /** The 'interfaces' array of the JVMS ClassFile structure. */
  private int[] interfaces;

  /**
   * The fields of this class, stored in a linked list of {@link FieldWriter}
   * linked via their {@link FieldWriter#fv} field. This field stores the first
   * element of this list.
   */
  private FieldWriter firstField;

  /**
   * The fields of this class, stored in a linked list of {@link FieldWriter}
   * linked via their {@link FieldWriter#fv} field. This field stores the last
   * element of this list.
   */
  private FieldWriter lastField;

  /**
   * The methods of this class, stored in a linked list of {@link MethodWriter}
   * linked via their {@link MethodWriter#mv} field. This field stores the first
   * element of this list.
   */
  private MethodWriter firstMethod;

  /**
   * The methods of this class, stored in a linked list of {@link MethodWriter}
   * linked via their {@link MethodWriter#mv} field. This field stores the last
   * element of this list.
   */
  private MethodWriter lastMethod;

  /** The number_of_classes field of the InnerClasses attribute, or 0. */
  private int numberOfInnerClasses;

  /** The 'classes' array of the InnerClasses attribute, or {@literal null}. */
  private ByteVector innerClasses;

  /** The class_index field of the EnclosingMethod attribute, or 0. */
  private int enclosingClassIndex;

  /** The method_index field of the EnclosingMethod attribute. */
  private int enclosingMethodIndex;

  /** The signature_index field of the Signature attribute, or 0. */
  private int signatureIndex;

  /** The source_file_index field of the SourceFile attribute, or 0. */
  private int sourceFileIndex;

  /**
   * The debug_extension field of the SourceDebugExtension attribute, or
   * {@literal null}.
   */
  private ByteVector debugExtension;

  /**
   * The last runtime visible annotation of this class. The previous ones can be
   * accessed with the {@link AnnotationWriter#previousAnnotation} field. May be
   * {@literal null}.
   */
  private AnnotationWriter lastRuntimeVisibleAnnotation;

  /**
   * The last runtime invisible annotation of this class. The previous ones can be
   * accessed with the {@link AnnotationWriter#previousAnnotation} field. May be
   * {@literal null}.
   */
  private AnnotationWriter lastRuntimeInvisibleAnnotation;

  /**
   * The last runtime visible type annotation of this class. The previous ones can
   * be accessed with the {@link AnnotationWriter#previousAnnotation} field. May
   * be {@literal null}.
   */
  private AnnotationWriter lastRuntimeVisibleTypeAnnotation;

  /**
   * The last runtime invisible type annotation of this class. The previous ones
   * can be accessed with the {@link AnnotationWriter#previousAnnotation} field.
   * May be {@literal null}.
   */
  private AnnotationWriter lastRuntimeInvisibleTypeAnnotation;

  /** The Module attribute of this class, or {@literal null}. */
  private ModuleWriter moduleWriter;

  /** The host_class_index field of the NestHost attribute, or 0. */
  private int nestHostClassIndex;

  /** The number_of_classes field of the NestMembers attribute, or 0. */
  private int numberOfNestMemberClasses;

  /** The 'classes' array of the NestMembers attribute, or {@literal null}. */
  private ByteVector nestMemberClasses;

  /**
   * The first non standard attribute of this class. The next ones can be accessed
   * with the {@link Attribute#nextAttribute} field. May be {@literal null}.
   *
   * <p>
   * <b>WARNING</b>: this list stores the attributes in the <i>reverse</i> order
   * of their visit. firstAttribute is actually the last attribute visited in
   * {@link #visitAttribute}. The {@link #toByteArray} method writes the
   * attributes in the order defined by this list, i.e. in the reverse order
   * specified by the user.
   */
  private Attribute firstAttribute;

  /**
   * Indicates what must be automatically computed in {@link MethodWriter}. Must
   * be one of {@link MethodWriter#COMPUTE_NOTHING},
   * {@link MethodWriter#COMPUTE_MAX_STACK_AND_LOCAL},
   * {@link MethodWriter#COMPUTE_INSERTED_FRAMES}, or
   * {@link MethodWriter#COMPUTE_ALL_FRAMES}.
   */
  private int compute;

  // -----------------------------------------------------------------------------------------------
  // Constructor
  // -----------------------------------------------------------------------------------------------

  /**
   * Constructs a new {@link ClassWriter} object.
   *
   * @param flags
   *         option flags that can be used to modify the default behavior of
   *         this class. Must be zero or more of {@link #COMPUTE_MAXS} and
   *         {@link #COMPUTE_FRAMES}.
   */
  public ClassWriter(final int flags) {
    this(null, flags);
  }

  /**
   * Constructs a new {@link ClassWriter} object and enables optimizations for
   * "mostly add" bytecode transformations. These optimizations are the following:
   *
   * <ul>
   * <li>The constant pool and bootstrap methods from the original class are
   * copied as is in the new class, which saves time. New constant pool entries
   * and new bootstrap methods will be added at the end if necessary, but unused
   * constant pool entries or bootstrap methods <i>won't be removed</i>.
   * <li>Methods that are not transformed are copied as is in the new class,
   * directly from the original class bytecode (i.e. without emitting visit events
   * for all the method instructions), which saves a <i>lot</i> of time.
   * Untransformed methods are detected by the fact that the {@link ClassReader}
   * receives {@link MethodVisitor} objects that come from a {@link ClassWriter}
   * (and not from any other {@link ClassVisitor} instance).
   * </ul>
   *
   * @param classReader
   *         the {@link ClassReader} used to read the original class. It will
   *         be used to copy the entire constant pool and bootstrap methods
   *         from the original class and also to copy other fragments of
   *         original bytecode where applicable.
   * @param flags
   *         option flags that can be used to modify the default behavior of
   *         this class.Must be zero or more of {@link #COMPUTE_MAXS} and
   *         {@link #COMPUTE_FRAMES}. <i>These option flags do not affect
   *         methods that are copied as is in the new class. This means that
   *         neither the maximum stack size nor the stack frames will be
   *         computed for these methods</i>.
   */
  public ClassWriter(final ClassReader classReader, final int flags) {
    symbolTable = classReader == null ? new SymbolTable(this) : new SymbolTable(this, classReader);
    if ((flags & COMPUTE_FRAMES) != 0) {
      this.compute = MethodWriter.COMPUTE_ALL_FRAMES;
    }
    else if ((flags & COMPUTE_MAXS) != 0) {
      this.compute = MethodWriter.COMPUTE_MAX_STACK_AND_LOCAL;
    }
    else {
      this.compute = MethodWriter.COMPUTE_NOTHING;
    }
  }

  // -----------------------------------------------------------------------------------------------
  // Implementation of the ClassVisitor abstract class
  // -----------------------------------------------------------------------------------------------

  @Override
  public final void visit(final int version, final int access, final String name, final String signature,
                          final String superName, final String[] interfaces) {
    this.version = version;
    this.accessFlags = access;
    this.thisClass = symbolTable.setMajorVersionAndClassName(version & 0xFFFF, name);
    if (signature != null) {
      this.signatureIndex = symbolTable.addConstantUtf8(signature);
    }
    this.superClass = superName == null ? 0 : symbolTable.addConstantClass(superName).index;
    if (interfaces != null && interfaces.length > 0) {
      interfaceCount = interfaces.length;
      this.interfaces = new int[interfaceCount];
      for (int i = 0; i < interfaceCount; ++i) {
        this.interfaces[i] = symbolTable.addConstantClass(interfaces[i]).index;
      }
    }
    if (compute == MethodWriter.COMPUTE_MAX_STACK_AND_LOCAL && (version & 0xFFFF) >= Opcodes.V1_7) {
      compute = MethodWriter.COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES;
    }
  }

  @Override
  public final void visitSource(final String file, final String debug) {
    if (file != null) {
      sourceFileIndex = symbolTable.addConstantUtf8(file);
    }
    if (debug != null) {
      debugExtension = new ByteVector().encodeUtf8(debug, 0, Integer.MAX_VALUE);
    }
  }

  @Override
  public final ModuleVisitor visitModule(final String name, final int access, final String version) {
    return moduleWriter = new ModuleWriter(symbolTable, symbolTable.addConstantModule(name).index, access, version == null ? 0
                                                                                                                           : symbolTable
                                                                                                                   .addConstantUtf8(
                                                                                                                           version));
  }

  @Override
  public void visitNestHost(final String nestHost) {
    nestHostClassIndex = symbolTable.addConstantClass(nestHost).index;
  }

  @Override
  public final void visitOuterClass(final String owner, final String name, final String descriptor) {
    enclosingClassIndex = symbolTable.addConstantClass(owner).index;
    if (name != null && descriptor != null) {
      enclosingMethodIndex = symbolTable.addConstantNameAndType(name, descriptor);
    }
  }

  @Override
  public final AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) {
    // Create a ByteVector to hold an 'annotation' JVMS structure.
    // See
    // https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.16.
    ByteVector annotation = new ByteVector();
    // Write type_index and reserve space for num_element_value_pairs.
    annotation.putShort(symbolTable.addConstantUtf8(descriptor)).putShort(0);
    if (visible) {
      return lastRuntimeVisibleAnnotation = new AnnotationWriter(symbolTable, annotation, lastRuntimeVisibleAnnotation);
    }
    else {
      return lastRuntimeInvisibleAnnotation = new AnnotationWriter(symbolTable, annotation, lastRuntimeInvisibleAnnotation);
    }
  }

  @Override
  public final AnnotationVisitor visitTypeAnnotation(final int typeRef, final TypePath typePath,
                                                     final String descriptor, final boolean visible) {
    // Create a ByteVector to hold a 'type_annotation' JVMS structure.
    // See
    // https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.20.
    ByteVector typeAnnotation = new ByteVector();
    // Write target_type, target_info, and target_path.
    TypeReference.putTarget(typeRef, typeAnnotation);
    TypePath.put(typePath, typeAnnotation);
    // Write type_index and reserve space for num_element_value_pairs.
    typeAnnotation.putShort(symbolTable.addConstantUtf8(descriptor)).putShort(0);
    if (visible) {
      return lastRuntimeVisibleTypeAnnotation = new AnnotationWriter(symbolTable, typeAnnotation, lastRuntimeVisibleTypeAnnotation);
    }
    else {
      return lastRuntimeInvisibleTypeAnnotation = new AnnotationWriter(symbolTable, typeAnnotation, lastRuntimeInvisibleTypeAnnotation);
    }
  }

  @Override
  public final void visitAttribute(final Attribute attribute) {
    // Store the attributes in the <i>reverse</i> order of their visit by this
    // method.
    attribute.nextAttribute = firstAttribute;
    firstAttribute = attribute;
  }

  @Override
  public void visitNestMember(final String nestMember) {
    if (nestMemberClasses == null) {
      nestMemberClasses = new ByteVector();
    }
    ++numberOfNestMemberClasses;
    nestMemberClasses.putShort(symbolTable.addConstantClass(nestMember).index);
  }

  @Override
  public final void visitInnerClass(final String name, final String outerName, final String innerName,
                                    final int access) {
    if (innerClasses == null) {
      innerClasses = new ByteVector();
    }
    // Section 4.7.6 of the JVMS states "Every CONSTANT_Class_info entry in the
    // constant_pool table
    // which represents a class or interface C that is not a package member must
    // have exactly one
    // corresponding entry in the classes array". To avoid duplicates we keep track
    // in the info
    // field of the Symbol of each CONSTANT_Class_info entry C whether an inner
    // class entry has
    // already been added for C. If so, we store the index of this inner class entry
    // (plus one) in
    // the info field. This trick allows duplicate detection in O(1) time.
    Symbol nameSymbol = symbolTable.addConstantClass(name);
    if (nameSymbol.info == 0) {
      ++numberOfInnerClasses;
      innerClasses.putShort(nameSymbol.index);
      innerClasses.putShort(outerName == null ? 0 : symbolTable.addConstantClass(outerName).index);
      innerClasses.putShort(innerName == null ? 0 : symbolTable.addConstantUtf8(innerName));
      innerClasses.putShort(access);
      nameSymbol.info = numberOfInnerClasses;
    }
    // Else, compare the inner classes entry nameSymbol.info - 1 with the arguments
    // of this method
    // and throw an exception if there is a difference?
  }

  @Override
  public final FieldVisitor visitField(final int access, final String name, final String descriptor,
                                       final String signature, final Object value) {
    FieldWriter fieldWriter = new FieldWriter(symbolTable, access, name, descriptor, signature, value);
    if (firstField == null) {
      firstField = fieldWriter;
    }
    else {
      lastField.fv = fieldWriter;
    }
    return lastField = fieldWriter;
  }

  @Override
  public final MethodVisitor visitMethod(final int access, final String name, final String descriptor,
                                         final String signature, final String[] exceptions) {
    MethodWriter methodWriter = new MethodWriter(symbolTable, access, name, descriptor, signature, exceptions, compute);
    if (firstMethod == null) {
      firstMethod = methodWriter;
    }
    else {
      lastMethod.mv = methodWriter;
    }
    return lastMethod = methodWriter;
  }

  @Override
  public final void visitEnd() {
    // Nothing to do.
  }

  // -----------------------------------------------------------------------------------------------
  // Other public methods
  // -----------------------------------------------------------------------------------------------

  /**
   * Returns the content of the class file that was built by this ClassWriter.
   *
   * @return the binary content of the JVMS ClassFile structure that was built by
   * this ClassWriter.
   *
   * @throws ClassTooLargeException
   *         if the constant pool of the class is too large.
   * @throws MethodTooLargeException
   *         if the Code attribute of a method is too large.
   */
  public byte[] toByteArray() throws ClassTooLargeException, MethodTooLargeException {
    // First step: compute the size in bytes of the ClassFile structure.
    // The magic field uses 4 bytes, 10 mandatory fields (minor_version,
    // major_version,
    // constant_pool_count, access_flags, this_class, super_class, interfaces_count,
    // fields_count,
    // methods_count and attributes_count) use 2 bytes each, and each interface uses
    // 2 bytes too.
    int size = 24 + 2 * interfaceCount;
    int fieldsCount = 0;
    FieldWriter fieldWriter = firstField;
    while (fieldWriter != null) {
      ++fieldsCount;
      size += fieldWriter.computeFieldInfoSize();
      fieldWriter = (FieldWriter) fieldWriter.fv;
    }
    int methodsCount = 0;
    MethodWriter methodWriter = firstMethod;
    while (methodWriter != null) {
      ++methodsCount;
      size += methodWriter.computeMethodInfoSize();
      methodWriter = (MethodWriter) methodWriter.mv;
    }
    // For ease of reference, we use here the same attribute order as in Section 4.7
    // of the JVMS.
    int attributesCount = 0;
    if (innerClasses != null) {
      ++attributesCount;
      size += 8 + innerClasses.length;
      symbolTable.addConstantUtf8(Constants.INNER_CLASSES);
    }
    if (enclosingClassIndex != 0) {
      ++attributesCount;
      size += 10;
      symbolTable.addConstantUtf8(Constants.ENCLOSING_METHOD);
    }
    if ((accessFlags & Opcodes.ACC_SYNTHETIC) != 0 && (version & 0xFFFF) < Opcodes.V1_5) {
      ++attributesCount;
      size += 6;
      symbolTable.addConstantUtf8(Constants.SYNTHETIC);
    }
    if (signatureIndex != 0) {
      ++attributesCount;
      size += 8;
      symbolTable.addConstantUtf8(Constants.SIGNATURE);
    }
    if (sourceFileIndex != 0) {
      ++attributesCount;
      size += 8;
      symbolTable.addConstantUtf8(Constants.SOURCE_FILE);
    }
    if (debugExtension != null) {
      ++attributesCount;
      size += 6 + debugExtension.length;
      symbolTable.addConstantUtf8(Constants.SOURCE_DEBUG_EXTENSION);
    }
    if ((accessFlags & Opcodes.ACC_DEPRECATED) != 0) {
      ++attributesCount;
      size += 6;
      symbolTable.addConstantUtf8(Constants.DEPRECATED);
    }
    if (lastRuntimeVisibleAnnotation != null) {
      ++attributesCount;
      size += lastRuntimeVisibleAnnotation.computeAnnotationsSize(Constants.RUNTIME_VISIBLE_ANNOTATIONS);
    }
    if (lastRuntimeInvisibleAnnotation != null) {
      ++attributesCount;
      size += lastRuntimeInvisibleAnnotation.computeAnnotationsSize(Constants.RUNTIME_INVISIBLE_ANNOTATIONS);
    }
    if (lastRuntimeVisibleTypeAnnotation != null) {
      ++attributesCount;
      size += lastRuntimeVisibleTypeAnnotation.computeAnnotationsSize(Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS);
    }
    if (lastRuntimeInvisibleTypeAnnotation != null) {
      ++attributesCount;
      size += lastRuntimeInvisibleTypeAnnotation.computeAnnotationsSize(Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS);
    }
    if (symbolTable.computeBootstrapMethodsSize() > 0) {
      ++attributesCount;
      size += symbolTable.computeBootstrapMethodsSize();
    }
    if (moduleWriter != null) {
      attributesCount += moduleWriter.getAttributeCount();
      size += moduleWriter.computeAttributesSize();
    }
    if (nestHostClassIndex != 0) {
      ++attributesCount;
      size += 8;
      symbolTable.addConstantUtf8(Constants.NEST_HOST);
    }
    if (nestMemberClasses != null) {
      ++attributesCount;
      size += 8 + nestMemberClasses.length;
      symbolTable.addConstantUtf8(Constants.NEST_MEMBERS);
    }
    if (firstAttribute != null) {
      attributesCount += firstAttribute.getAttributeCount();
      size += firstAttribute.computeAttributesSize(symbolTable);
    }
    // IMPORTANT: this must be the last part of the ClassFile size computation,
    // because the previous
    // statements can add attribute names to the constant pool, thereby changing its
    // size!
    size += symbolTable.getConstantPoolLength();
    int constantPoolCount = symbolTable.getConstantPoolCount();
    if (constantPoolCount > 0xFFFF) {
      throw new ClassTooLargeException(symbolTable.getClassName(), constantPoolCount);
    }

    // Second step: allocate a ByteVector of the correct size (in order to avoid any
    // array copy in
    // dynamic resizes) and fill it with the ClassFile content.
    ByteVector result = new ByteVector(size);
    result.putInt(0xCAFEBABE).putInt(version);
    symbolTable.putConstantPool(result);
    int mask = (version & 0xFFFF) < Opcodes.V1_5 ? Opcodes.ACC_SYNTHETIC : 0;
    result.putShort(accessFlags & ~mask).putShort(thisClass).putShort(superClass);
    result.putShort(interfaceCount);
    for (int i = 0; i < interfaceCount; ++i) {
      result.putShort(interfaces[i]);
    }
    result.putShort(fieldsCount);
    fieldWriter = firstField;
    while (fieldWriter != null) {
      fieldWriter.putFieldInfo(result);
      fieldWriter = (FieldWriter) fieldWriter.fv;
    }
    result.putShort(methodsCount);
    boolean hasFrames = false;
    boolean hasAsmInstructions = false;
    methodWriter = firstMethod;
    while (methodWriter != null) {
      hasFrames |= methodWriter.hasFrames();
      hasAsmInstructions |= methodWriter.hasAsmInstructions();
      methodWriter.putMethodInfo(result);
      methodWriter = (MethodWriter) methodWriter.mv;
    }
    // For ease of reference, we use here the same attribute order as in Section 4.7
    // of the JVMS.
    result.putShort(attributesCount);
    if (innerClasses != null) {
      result.putShort(symbolTable.addConstantUtf8(Constants.INNER_CLASSES)).putInt(innerClasses.length + 2).putShort(
              numberOfInnerClasses).putByteArray(innerClasses.data, 0, innerClasses.length);
    }
    if (enclosingClassIndex != 0) {
      result.putShort(symbolTable.addConstantUtf8(Constants.ENCLOSING_METHOD)).putInt(4).putShort(enclosingClassIndex).putShort(
              enclosingMethodIndex);
    }
    if ((accessFlags & Opcodes.ACC_SYNTHETIC) != 0 && (version & 0xFFFF) < Opcodes.V1_5) {
      result.putShort(symbolTable.addConstantUtf8(Constants.SYNTHETIC)).putInt(0);
    }
    if (signatureIndex != 0) {
      result.putShort(symbolTable.addConstantUtf8(Constants.SIGNATURE)).putInt(2).putShort(signatureIndex);
    }
    if (sourceFileIndex != 0) {
      result.putShort(symbolTable.addConstantUtf8(Constants.SOURCE_FILE)).putInt(2).putShort(sourceFileIndex);
    }
    if (debugExtension != null) {
      int length = debugExtension.length;
      result.putShort(symbolTable.addConstantUtf8(Constants.SOURCE_DEBUG_EXTENSION)).putInt(length).putByteArray(debugExtension.data,
                                                                                                                 0, length);
    }
    if ((accessFlags & Opcodes.ACC_DEPRECATED) != 0) {
      result.putShort(symbolTable.addConstantUtf8(Constants.DEPRECATED)).putInt(0);
    }
    if (lastRuntimeVisibleAnnotation != null) {
      lastRuntimeVisibleAnnotation.putAnnotations(symbolTable.addConstantUtf8(Constants.RUNTIME_VISIBLE_ANNOTATIONS), result);
    }
    if (lastRuntimeInvisibleAnnotation != null) {
      lastRuntimeInvisibleAnnotation.putAnnotations(symbolTable.addConstantUtf8(Constants.RUNTIME_INVISIBLE_ANNOTATIONS), result);
    }
    if (lastRuntimeVisibleTypeAnnotation != null) {
      lastRuntimeVisibleTypeAnnotation.putAnnotations(symbolTable.addConstantUtf8(Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS),
                                                      result);
    }
    if (lastRuntimeInvisibleTypeAnnotation != null) {
      lastRuntimeInvisibleTypeAnnotation.putAnnotations(symbolTable.addConstantUtf8(Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS),
                                                        result);
    }
    symbolTable.putBootstrapMethods(result);
    if (moduleWriter != null) {
      moduleWriter.putAttributes(result);
    }
    if (nestHostClassIndex != 0) {
      result.putShort(symbolTable.addConstantUtf8(Constants.NEST_HOST)).putInt(2).putShort(nestHostClassIndex);
    }
    if (nestMemberClasses != null) {
      result.putShort(symbolTable.addConstantUtf8(Constants.NEST_MEMBERS)).putInt(nestMemberClasses.length + 2).putShort(
              numberOfNestMemberClasses).putByteArray(nestMemberClasses.data, 0, nestMemberClasses.length);
    }
    if (firstAttribute != null) {
      firstAttribute.putAttributes(symbolTable, result);
    }

    // Third step: replace the ASM specific instructions, if any.
    if (hasAsmInstructions) {
      return replaceAsmInstructions(result.data, hasFrames);
    }
    else {
      return result.data;
    }
  }

  /**
   * Returns the equivalent of the given class file, with the ASM specific
   * instructions replaced with standard ones. This is done with a ClassReader
   * -&gt; ClassWriter round trip.
   *
   * @param classFile
   *         a class file containing ASM specific instructions, generated by
   *         this ClassWriter.
   * @param hasFrames
   *         whether there is at least one stack map frames in 'classFile'.
   *
   * @return an equivalent of 'classFile', with the ASM specific instructions
   * replaced with standard ones.
   */
  private byte[] replaceAsmInstructions(final byte[] classFile, final boolean hasFrames) {
    final Attribute[] attributes = getAttributePrototypes();
    firstField = null;
    lastField = null;
    firstMethod = null;
    lastMethod = null;
    lastRuntimeVisibleAnnotation = null;
    lastRuntimeInvisibleAnnotation = null;
    lastRuntimeVisibleTypeAnnotation = null;
    lastRuntimeInvisibleTypeAnnotation = null;
    moduleWriter = null;
    nestHostClassIndex = 0;
    numberOfNestMemberClasses = 0;
    nestMemberClasses = null;
    firstAttribute = null;
    compute = hasFrames ? MethodWriter.COMPUTE_INSERTED_FRAMES : MethodWriter.COMPUTE_NOTHING;
    new ClassReader(classFile, 0, /* checkClassVersion = */ false).accept(this, attributes,
                                                                          (hasFrames ? ClassReader.EXPAND_FRAMES
                                                                                     : 0) | ClassReader.EXPAND_ASM_INSNS);
    return toByteArray();
  }

  /**
   * Returns the prototypes of the attributes used by this class, its fields and
   * its methods.
   *
   * @return the prototypes of the attributes used by this class, its fields and
   * its methods.
   */
  private Attribute[] getAttributePrototypes() {
    Attribute.Set attributePrototypes = new Attribute.Set();
    attributePrototypes.addAttributes(firstAttribute);
    FieldWriter fieldWriter = firstField;
    while (fieldWriter != null) {
      fieldWriter.collectAttributePrototypes(attributePrototypes);
      fieldWriter = (FieldWriter) fieldWriter.fv;
    }
    MethodWriter methodWriter = firstMethod;
    while (methodWriter != null) {
      methodWriter.collectAttributePrototypes(attributePrototypes);
      methodWriter = (MethodWriter) methodWriter.mv;
    }
    return attributePrototypes.toArray();
  }

  // -----------------------------------------------------------------------------------------------
  // Utility methods: constant pool management for Attribute sub classes
  // -----------------------------------------------------------------------------------------------

  /**
   * Adds a number or string constant to the constant pool of the class being
   * build. Does nothing if the constant pool already contains a similar item.
   * <i>This method is intended for {@link Attribute} sub classes, and is normally
   * not needed by class generators or adapters.</i>
   *
   * @param value
   *         the value of the constant to be added to the constant pool. This
   *         parameter must be an {@link Integer}, a {@link Float}, a
   *         {@link Long}, a {@link Double} or a {@link String}.
   *
   * @return the index of a new or already existing constant item with the given
   * value.
   */
  public int newConst(final Object value) {
    return symbolTable.addConstant(value).index;
  }

  /**
   * Adds an UTF8 string to the constant pool of the class being build. Does
   * nothing if the constant pool already contains a similar item. <i>This method
   * is intended for {@link Attribute} sub classes, and is normally not needed by
   * class generators or adapters.</i>
   *
   * @param value
   *         the String value.
   *
   * @return the index of a new or already existing UTF8 item.
   */
  // DontCheck(AbbreviationAsWordInName): can't be renamed (for backward binary
  // compatibility).
  public int newUTF8(final String value) {
    return symbolTable.addConstantUtf8(value);
  }

  /**
   * Adds a class reference to the constant pool of the class being build. Does
   * nothing if the constant pool already contains a similar item. <i>This method
   * is intended for {@link Attribute} sub classes, and is normally not needed by
   * class generators or adapters.</i>
   *
   * @param value
   *         the internal name of the class.
   *
   * @return the index of a new or already existing class reference item.
   */
  public int newClass(final String value) {
    return symbolTable.addConstantClass(value).index;
  }

  /**
   * Adds a method type reference to the constant pool of the class being build.
   * Does nothing if the constant pool already contains a similar item. <i>This
   * method is intended for {@link Attribute} sub classes, and is normally not
   * needed by class generators or adapters.</i>
   *
   * @param methodDescriptor
   *         method descriptor of the method type.
   *
   * @return the index of a new or already existing method type reference item.
   */
  public int newMethodType(final String methodDescriptor) {
    return symbolTable.addConstantMethodType(methodDescriptor).index;
  }

  /**
   * Adds a module reference to the constant pool of the class being build. Does
   * nothing if the constant pool already contains a similar item. <i>This method
   * is intended for {@link Attribute} sub classes, and is normally not needed by
   * class generators or adapters.</i>
   *
   * @param moduleName
   *         name of the module.
   *
   * @return the index of a new or already existing module reference item.
   */
  public int newModule(final String moduleName) {
    return symbolTable.addConstantModule(moduleName).index;
  }

  /**
   * Adds a package reference to the constant pool of the class being build. Does
   * nothing if the constant pool already contains a similar item. <i>This method
   * is intended for {@link Attribute} sub classes, and is normally not needed by
   * class generators or adapters.</i>
   *
   * @param packageName
   *         name of the package in its internal form.
   *
   * @return the index of a new or already existing module reference item.
   */
  public int newPackage(final String packageName) {
    return symbolTable.addConstantPackage(packageName).index;
  }

  /**
   * Adds a handle to the constant pool of the class being build. Does nothing if
   * the constant pool already contains a similar item. <i>This method is intended
   * for {@link Attribute} sub classes, and is normally not needed by class
   * generators or adapters.</i>
   *
   * @param tag
   *         the kind of this handle. Must be {@link Opcodes#H_GETFIELD},
   *         {@link Opcodes#H_GETSTATIC}, {@link Opcodes#H_PUTFIELD},
   *         {@link Opcodes#H_PUTSTATIC}, {@link Opcodes#H_INVOKEVIRTUAL},
   *         {@link Opcodes#H_INVOKESTATIC}, {@link Opcodes#H_INVOKESPECIAL},
   *         {@link Opcodes#H_NEWINVOKESPECIAL} or
   *         {@link Opcodes#H_INVOKEINTERFACE}.
   * @param owner
   *         the internal name of the field or method owner class.
   * @param name
   *         the name of the field or method.
   * @param descriptor
   *         the descriptor of the field or method.
   *
   * @return the index of a new or already existing method type reference item.
   *
   * @deprecated this method is superseded by
   * {@link #newHandle(int, String, String, String, boolean)}.
   */
  @Deprecated
  public int newHandle(final int tag, final String owner, final String name, final String descriptor) {
    return newHandle(tag, owner, name, descriptor, tag == Opcodes.H_INVOKEINTERFACE);
  }

  /**
   * Adds a handle to the constant pool of the class being build. Does nothing if
   * the constant pool already contains a similar item. <i>This method is intended
   * for {@link Attribute} sub classes, and is normally not needed by class
   * generators or adapters.</i>
   *
   * @param tag
   *         the kind of this handle. Must be {@link Opcodes#H_GETFIELD},
   *         {@link Opcodes#H_GETSTATIC}, {@link Opcodes#H_PUTFIELD},
   *         {@link Opcodes#H_PUTSTATIC}, {@link Opcodes#H_INVOKEVIRTUAL},
   *         {@link Opcodes#H_INVOKESTATIC}, {@link Opcodes#H_INVOKESPECIAL},
   *         {@link Opcodes#H_NEWINVOKESPECIAL} or
   *         {@link Opcodes#H_INVOKEINTERFACE}.
   * @param owner
   *         the internal name of the field or method owner class.
   * @param name
   *         the name of the field or method.
   * @param descriptor
   *         the descriptor of the field or method.
   * @param isInterface
   *         true if the owner is an interface.
   *
   * @return the index of a new or already existing method type reference item.
   */
  public int newHandle(final int tag, final String owner, final String name, final String descriptor,
                       final boolean isInterface) {
    return symbolTable.addConstantMethodHandle(tag, owner, name, descriptor, isInterface).index;
  }

  /**
   * Adds a dynamic constant reference to the constant pool of the class being
   * build. Does nothing if the constant pool already contains a similar item.
   * <i>This method is intended for {@link Attribute} sub classes, and is normally
   * not needed by class generators or adapters.</i>
   *
   * @param name
   *         name of the invoked method.
   * @param descriptor
   *         field descriptor of the constant type.
   * @param bootstrapMethodHandle
   *         the bootstrap method.
   * @param bootstrapMethodArguments
   *         the bootstrap method constant arguments.
   *
   * @return the index of a new or already existing dynamic constant reference
   * item.
   */
  public int newConstantDynamic(final String name, final String descriptor, final Handle bootstrapMethodHandle,
                                final Object... bootstrapMethodArguments) {
    return symbolTable.addConstantDynamic(name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments).index;
  }

  /**
   * Adds an invokedynamic reference to the constant pool of the class being
   * build. Does nothing if the constant pool already contains a similar item.
   * <i>This method is intended for {@link Attribute} sub classes, and is normally
   * not needed by class generators or adapters.</i>
   *
   * @param name
   *         name of the invoked method.
   * @param descriptor
   *         descriptor of the invoke method.
   * @param bootstrapMethodHandle
   *         the bootstrap method.
   * @param bootstrapMethodArguments
   *         the bootstrap method constant arguments.
   *
   * @return the index of a new or already existing invokedynamic reference item.
   */
  public int newInvokeDynamic(final String name, final String descriptor, final Handle bootstrapMethodHandle,
                              final Object... bootstrapMethodArguments) {
    return symbolTable.addConstantInvokeDynamic(name, descriptor, bootstrapMethodHandle,
                                                bootstrapMethodArguments).index;
  }

  /**
   * Adds a field reference to the constant pool of the class being build. Does
   * nothing if the constant pool already contains a similar item. <i>This method
   * is intended for {@link Attribute} sub classes, and is normally not needed by
   * class generators or adapters.</i>
   *
   * @param owner
   *         the internal name of the field's owner class.
   * @param name
   *         the field's name.
   * @param descriptor
   *         the field's descriptor.
   *
   * @return the index of a new or already existing field reference item.
   */
  public int newField(final String owner, final String name, final String descriptor) {
    return symbolTable.addConstantFieldref(owner, name, descriptor).index;
  }

  /**
   * Adds a method reference to the constant pool of the class being build. Does
   * nothing if the constant pool already contains a similar item. <i>This method
   * is intended for {@link Attribute} sub classes, and is normally not needed by
   * class generators or adapters.</i>
   *
   * @param owner
   *         the internal name of the method's owner class.
   * @param name
   *         the method's name.
   * @param descriptor
   *         the method's descriptor.
   * @param isInterface
   *         {@literal true} if {@code owner} is an interface.
   *
   * @return the index of a new or already existing method reference item.
   */
  public int newMethod(final String owner, final String name, final String descriptor, final boolean isInterface) {
    return symbolTable.addConstantMethodref(owner, name, descriptor, isInterface).index;
  }

  /**
   * Adds a name and type to the constant pool of the class being build. Does
   * nothing if the constant pool already contains a similar item. <i>This method
   * is intended for {@link Attribute} sub classes, and is normally not needed by
   * class generators or adapters.</i>
   *
   * @param name
   *         a name.
   * @param descriptor
   *         a type descriptor.
   *
   * @return the index of a new or already existing name and type item.
   */
  public int newNameType(final String name, final String descriptor) {
    return symbolTable.addConstantNameAndType(name, descriptor);
  }

  // -----------------------------------------------------------------------------------------------
  // Default method to compute common super classes when computing stack map
  // frames
  // -----------------------------------------------------------------------------------------------

  /**
   * Returns the common super type of the two given types. The default
   * implementation of this method <i>loads</i> the two given classes and uses the
   * java.lang.Class methods to find the common super class. It can be overridden
   * to compute this common super type in other ways, in particular without
   * actually loading any class, or to take into account the class that is
   * currently being generated by this ClassWriter, which can of course not be
   * loaded since it is under construction.
   *
   * @param type1
   *         the internal name of a class.
   * @param type2
   *         the internal name of another class.
   *
   * @return the internal name of the common super class of the two given classes.
   */
  protected String getCommonSuperClass(final String type1, final String type2) {
    ClassLoader classLoader = getClassLoader();
    Class<?> class1;
    try {
      class1 = Class.forName(type1.replace('/', '.'), false, classLoader);
    }
    catch (ClassNotFoundException e) {
      throw new TypeNotPresentException(type1, e);
    }
    Class<?> class2;
    try {
      class2 = Class.forName(type2.replace('/', '.'), false, classLoader);
    }
    catch (ClassNotFoundException e) {
      throw new TypeNotPresentException(type2, e);
    }
    if (class1.isAssignableFrom(class2)) {
      return type1;
    }
    if (class2.isAssignableFrom(class1)) {
      return type2;
    }
    if (class1.isInterface() || class2.isInterface()) {
      return "java/lang/Object";
    }
    else {
      do {
        class1 = class1.getSuperclass();
      }
      while (!class1.isAssignableFrom(class2));
      return class1.getName().replace('.', '/');
    }
  }

  /**
   * Returns the {@link ClassLoader} to be used by the default implementation of
   * {@link #getCommonSuperClass(String, String)}, that of this
   * {@link ClassWriter}'s runtime type by default.
   *
   * @return ClassLoader
   */
  protected ClassLoader getClassLoader() {
    return getClass().getClassLoader();
  }
}
